package com.caj.redis;

import com.caj.item.Item;
import com.caj.item.LockItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/*
    redis分布式锁
 */
@Component
public class RedisDistLock implements Lock {

    private Thread currentOwner;

    @Autowired
    private JedisPool jedisPool;

    //分布式锁在Redis中使用的命名空间前缀
    private final static String RD_DISTLOCK_NS = "tdln:";

    //锁的时间为1秒
    private final static int LOCK_TIME_5000_MS = 5000;

    //锁的名称
    private String lockName = "lock";

    //每个线程独有的变量
    private ThreadLocal<String> threadLocal = new ThreadLocal<>();

    //延迟队列
    private DelayQueue<Item<LockItem>> delayQueue = new DelayQueue<>();

    //看门狗线程
    private Thread watchThread;

    //解锁的Lua脚本
    private final static String RELEASE_LOCK_LUA =
            "if redis.call('get',KEYS[1])==ARGV[1] then\n" +
                    "return redis.call('del', KEYS[1])\n" +
                    "else return 0 end";

    //续期的Lua脚本
    private final static String SUBSCRIBE_LOCK_LUA =
            "if redis.call('get',KEYS[1])==ARGV[1] then\n" +
                    "return redis.call('pexpire', KEYS[1], ARGV[2])\n" +
                    "else return 0 end";

    @Override
    public void lock() {
        //若未获取到锁，则阻塞等待
        while(!tryLock()){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public boolean tryLock() {
        if(currentOwner == Thread.currentThread()){
            //锁的可重入
            return true;
        } else if (currentOwner != null) {
            //本应用的其他线程已获取到锁
            return false;
        }
        //本应用未有线程获取到锁，从redis获取锁
        Jedis jedis = jedisPool.getResource();
        //锁名：tdln:lock
        //锁值：UUID
        String id = UUID.randomUUID().toString();
        //redis键的属性 set key value ex 1 nx
        SetParams param = new SetParams();
        //键的生存时间为1秒
        param.px(LOCK_TIME_5000_MS);
        //键不存在才塞值
        param.nx();
        try{
            if("OK".equals(jedis.set(RD_DISTLOCK_NS+lockName, id, param))){
                //分布式加锁成功:在threadloacl中保存id，并设置当前currentOwner为当前线程
                threadLocal.set(id);
                currentOwner = Thread.currentThread();
                System.out.println(Thread.currentThread() + "加锁成功");

                //加入看门狗，给锁续时间。只需要一个Thread，故将其设为成员变量
                if(watchThread == null){
                    watchThread = new Thread(new ExpireTask());
                    watchThread.setDaemon(true);
                    watchThread.start();
                }
                //将之前队列中的数据清除
                delayQueue.clear();
                delayQueue.add(new Item<>(LOCK_TIME_5000_MS, new LockItem(RD_DISTLOCK_NS+lockName, id)));

                return true;
            }else {
                System.out.println(Thread.currentThread() + "加锁失败");
                return false;
            }
        }catch (Exception e){
            throw new RuntimeException("分布式锁加锁失败", e);
        }finally {
            jedis.close();
        }
    }

    @Override
    public void unlock() {
        //当前线程不是加锁的线程，此时想解锁，直接报错
        if(currentOwner == null || currentOwner != Thread.currentThread()){
            throw new RuntimeException("当前线程不是加锁的线程");
        }
        Jedis jedis = null;
        try{
            jedis = jedisPool.getResource();
            long result = (long)jedis.eval(RELEASE_LOCK_LUA, Arrays.asList(RD_DISTLOCK_NS + lockName),
                    Arrays.asList(threadLocal.get()));

            if(0L != result){
                //解锁成功
                System.out.println(Thread.currentThread() + String.valueOf(result) + "解锁成功");
            }else {
                System.out.println(Thread.currentThread() + String.valueOf(result)  + "锁已释放，无需解锁");
            }
        }catch (Exception e){
            throw new RuntimeException(Thread.currentThread() + "分布式锁释放锁失败", e);
        }finally {
            if(jedis != null){
                jedis.close();
            }
            threadLocal.remove();
            currentOwner = null;
        }

    }

    private class ExpireTask implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread() + "看门狗开始启动...");
            while(!Thread.currentThread().isInterrupted()){
                Jedis jedis = null;
                try {
                    LockItem lockItem = delayQueue.take().getData();
                    //去除锁元素后，给其续期
                    jedis = jedisPool.getResource();
                    if(0L == (long)jedis.eval(SUBSCRIBE_LOCK_LUA, Arrays.asList(lockItem.getKey()),
                            Arrays.asList(lockItem.getValue(), String.valueOf(LOCK_TIME_5000_MS)))){
                        System.out.println(Thread.currentThread() + "锁已释放，无需续期" + " lockId:" + lockItem.getValue());
                    }else {
                        delayQueue.add(new Item<>(LOCK_TIME_5000_MS,
                                new LockItem(lockItem.getKey(), lockItem.getValue())));
                        System.out.println(Thread.currentThread() + "锁续期成功" + LOCK_TIME_5000_MS + "ms" + " lockId:" + lockItem.getValue());
                    }

                } catch (InterruptedException e) {
                    System.out.println("看门狗线程被中断...");
                    break;
                }finally {
                    jedis.close();
                }
            }
            System.out.println(Thread.currentThread() + "看门狗关闭...");
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        throw new UnsupportedOperationException("不支持可中断获取锁！");
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        throw new UnsupportedOperationException("不支持等待尝试获取锁！");
    }

    @Override
    public Condition newCondition() {
        throw new UnsupportedOperationException("不支持等待通知操作！");
    }
}
